package rpc

import (
	"context"
	"errors"
	"github.com/smallnest/rpcx/client"
	"sync"
)

//ErrNilRPC means a nil RPC
var ErrNilRPC = errors.New("rpc: nil RPC")

//ErrNilCreator means a RPC don`t have a vaild CreateClient
var ErrNilCreator = errors.New("rpc: nil CreateClient")

type clientCreator = func(string) (client.XClient, error)

//RPC clients
type RPC struct {
	mu           sync.RWMutex
	clients      map[string]client.XClient
	CreateClient clientCreator
}

//New return a new RPC
func New(creator clientCreator) *RPC {
	rpc := new(RPC)
	rpc.clients = make(map[string]client.XClient)
	rpc.CreateClient = creator
	return rpc
}

func (r *RPC) createClient(name string) (c client.XClient, err error) {
	if r == nil {
		return nil, ErrNilRPC
	}
	if r.CreateClient == nil {
		return nil, ErrNilCreator
	}
	return r.CreateClient(name)
}

//Client return a rpc client
func (r *RPC) Client(name string) (c client.XClient, err error) {
	if r == nil {
		return nil, ErrNilRPC
	}
	r.mu.RLock()
	c = r.clients[name]
	r.mu.RUnlock()
	if c == nil {
		r.mu.Lock()
		defer r.mu.Unlock()
		c = r.clients[name]
		if c != nil {
			return
		}
		c, err = r.createClient(name)
		r.clients[name] = c
	}
	return
}

//Close clients
func (r *RPC) Close(names ...string) {
	if r == nil {
		return
	}
	r.mu.Lock()
	defer r.mu.Unlock()
	for _, name := range names {
		c := r.clients[name]
		if c == nil {
			continue
		}
		c.Close()
		delete(r.clients, name)
	}
}

//ReplaceClients delete client[names...] and create new
//return new clients and error, if error is not nil, clients will be nil
func (r *RPC) ReplaceClients(names ...string) (c []client.XClient, err error) {
	if r == nil {
		return nil, ErrNilRPC
	}
	r.mu.Lock()
	defer r.mu.Unlock()
	i := 0
	size := len(names)
	ret := make([]client.XClient, size)
	for i = 0; i < size; i++ {
		ret[i], err = r.createClient(names[i])
		if err != nil {
			for j := 0; j < i; j++ {
				ret[j].Close()
			}
			return nil, err
		}
	}
	for i = 0; i < size; i++ {
		old, ok := r.clients[names[i]]
		if ok && old != nil {
			old.Close()
		}
		r.clients[names[i]] = ret[i]
	}
	return ret, nil
}

//Call server[serverName]`s method[methodName] use args & reply, and return error
func (r *RPC) Call(serverName, methodName string, args interface{}, reply interface{}) error {
	if r == nil {
		return ErrNilRPC
	}
	c, err := r.Client(serverName)
	if err != nil {
		return err
	}
	return c.Call(context.Background(), methodName, args, reply)
}

func (r *RPC) SetClient(name string, c client.XClient) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.clients[name] = c
}
